package com.apes.framework.plugin.fsm.core;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.apes.framework.api.SpringManager;
import com.apes.framework.config.redis.RedisMessageListener;
import com.apes.framework.jpa.ApesBean;
import com.apes.framework.jpa.spec.JpaDsl;
import com.apes.framework.plugin.cache.Cache;
import com.apes.framework.plugin.cache.MemoryCache;
import com.apes.framework.plugin.esb.api.SimpleRequest;
import com.apes.framework.plugin.event.Listener;
import com.apes.framework.plugin.fsm.*;
import com.apes.framework.plugin.fsm.util.FsmUtil;
import com.apes.framework.plugin.fsm.util.TaskParam;
import com.apes.framework.plugin.outbox.OutboxSubscriber;
import com.apes.framework.plugin.outbox.OutboxSubscriberManager;
import com.apes.framework.plugin.todo.TodoTaskService;
import com.apes.framework.rop.session.store.ApesSession;
import com.apes.framework.rop.session.store.ApesSessionRepository;
import com.apes.framework.util.CacheUtil;
import com.apes.framework.util.DateUtil;
import com.apes.framework.util.JpaUtil;
import com.google.common.base.Strings;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.squirrelframework.foundation.fsm.*;
import org.squirrelframework.foundation.fsm.builder.On;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class FsmManager {
    private final String CONST_DEFAULT_CACHE_NAME = "fsm.outboxSubscriberCache";
    private static final Cache cache = MemoryCache.getInstance();

    @Autowired
    private FsmFlowRepository fsmFlowRepository;

    @Autowired
    private FsmFlowInstanceRepository fsmFlowInstRepository;

    @Autowired
    private FsmStateInstanceRepository fsmStateInstanceRepository;

    @Autowired
    private FsmStateInstanceTaskRepository taskRepository;

    /**
     * 订阅状态机配置变更事件
     **/
    @RedisMessageListener(topic = FsmConfig.FSM_SETTING_TOPIC)
    public void subscribe(String fsmFlowId) {
        /**清除状态机构造器**/
        removeStateMachineBuilder(fsmFlowId);

        /**清除与状态流相关订阅**/
        List<OutboxSubscriber> outboxSubscribers = SpringManager.getBean(OutboxSubscriberManager.class).getLocalOutboxSubscribers();
        List<OutboxSubscriber> fsmFlowOutBoxSubscribers = getFsmFlowSubscribers(fsmFlowId);
        fsmFlowOutBoxSubscribers.stream().forEach(os -> outboxSubscribers.removeIf(subscriber -> subscriber.equals(os)));
        fsmFlowOutBoxSubscribers.clear();

        /**重新添加状态流相关订阅**/
        FsmFlow fsmFlow = fsmFlowRepository.findOne(fsmFlowId);
        if (fsmFlow == null) {
            return;
        }
        if (fsmFlow.isValid()) {
            initOutboxSubscriber(outboxSubscribers, fsmFlow);
        }
    }

    /**
     * 初始化事务发件箱订阅者：所有有效的状态机
     **/
    @Listener(topic = "outbox.subscriber.local.init")
    public List<OutboxSubscriber> initOutboxSubscribers(SimpleRequest request) {
        List<OutboxSubscriber> outboxSubscribers = new ArrayList<>();
        fsmFlowRepository.findAll(JpaDsl.toCriteriaByEq("valid", true)).stream()
                .forEach(fsmFlow -> initOutboxSubscriber(outboxSubscribers, fsmFlow));
        return outboxSubscribers;
    }

    private void initOutboxSubscriber(List<OutboxSubscriber> outboxSubscribers, FsmFlow fsmFlow) {
        OutboxSubscriber subscriber = new OutboxSubscriber();
        Class aggregateType = JpaUtil.getDomainClass(fsmFlow.getBusinessType().getId());
        if (aggregateType == null) {
            return;
        }
        String id = String.format("fsm.%s.save.%s", aggregateType.getSimpleName(), fsmFlow.getId());
        subscriber.setId(id);
        subscriber.setName("状态流事件拦截：" + aggregateType.getSimpleName());
        subscriber.setEventType("S");
        subscriber.setAggregateType(aggregateType.getName());
        subscriber.setProcessor(o -> {
            SpringManager.getBean(FsmEngine.class).fire(fsmFlow.getId(), o);
            return null;
        });
        if (StringUtils.hasText(fsmFlow.getCondition())) {
            subscriber.setCondition(fsmFlow.getCondition());
        }
        outboxSubscribers.add(subscriber);
        addFsmFlowSubscriber(fsmFlow, subscriber);

        subscriber = new OutboxSubscriber();
        id = String.format("fsm.%s.delete.%s", aggregateType.getSimpleName(), fsmFlow.getId());
        subscriber.setId(id);
        subscriber.setName("状态流事件拦截：" + aggregateType.getSimpleName());
        subscriber.setEventType("D");
        subscriber.setAggregateType(aggregateType.getName());
        subscriber.setProcessor(o -> {
            SpringManager.getBean(TodoTaskService.class).stopTask((ApesBean) o);
            return null;
        });
        outboxSubscribers.add(subscriber);
        addFsmFlowSubscriber(fsmFlow, subscriber);

        String subscriberId = String.format("todo.finish.%s", aggregateType.getSimpleName());
        if (outboxSubscribers.stream().filter(o -> o.getId().equals(subscriberId)).count() == 0) {
            subscriber = new OutboxSubscriber();
            subscriber.setId(subscriberId);
            subscriber.setName("待办处理事件拦截：" + aggregateType.getSimpleName());
            subscriber.setEventType("S");
            subscriber.setAggregateType(aggregateType.getName());
            subscriber.setProcessor(o -> {
                SpringManager.getBean(TodoTaskService.class).finishTask((ApesBean) o);
                return null;
            });
            outboxSubscribers.add(subscriber);
        }
    }

    private void addFsmFlowSubscriber(FsmFlow fsmFlow, OutboxSubscriber subscriber) {
        List<OutboxSubscriber> fsmFlowOutBoxSubscribers = getFsmFlowSubscribers(fsmFlow.getId());
        fsmFlowOutBoxSubscribers.removeIf(sc -> sc.getId().equals(subscriber.getId()));
        fsmFlowOutBoxSubscribers.add(subscriber);
    }

    private List<OutboxSubscriber> getFsmFlowSubscribers(String fsmFlowId) {
        Map fsmOutBoxSubscribers = getFsmOutBoxSubscribers();
        List<OutboxSubscriber> fsmFlowOutBoxSubscribers = (List<OutboxSubscriber>) fsmOutBoxSubscribers.get(fsmFlowId);
        if (fsmFlowOutBoxSubscribers == null) {
            fsmFlowOutBoxSubscribers = new ArrayList<>();
            fsmOutBoxSubscribers.put(fsmFlowId, fsmFlowOutBoxSubscribers);
        }
        return fsmFlowOutBoxSubscribers;
    }

    private Map getFsmOutBoxSubscribers() {
        Map fsmOutboxSubscribers = (Map) cache.get(CONST_DEFAULT_CACHE_NAME);
        if (fsmOutboxSubscribers == null) {
            fsmOutboxSubscribers = new HashMap();
            cache.put(CONST_DEFAULT_CACHE_NAME, fsmOutboxSubscribers);
        }
        return fsmOutboxSubscribers;
    }

    /**
     * 创建状态机构造器
     **/
    private StateMachineBuilder createStateMachineBuilder(FsmFlow fsmFlow) {
        String k = "fsm_builder_" + fsmFlow.getId();
        StateMachineBuilder builder = (StateMachineBuilder) cache.get(k);
        if (builder == null) {
            builder = StateMachineBuilderFactory.create(FsmStateMachine.class, FsmState.class, FsmEvent.class, Map.class);
            List<FsmState> fsmStates = fsmFlow.findAllFsmStateByFsmFlow();
            for (FsmState state : fsmStates) {
                state.getTasks();
                MutableState newState = builder.defineState(state);
                newState.setCompositeType(state.convertCompositeType());
                if (!newState.isParallelState()) {
                    newState.setHistoryType(HistoryType.NONE);
                }
                newState.setFinal(state.isFinish());
                if (state.getParent() != null) {
                    MutableState parentState = builder.defineState(state.getParent());
                    newState.setParentState(parentState);
                    parentState.addChildState(newState);
                    if (!parentState.isParallelState() && state.isStarted()) {
                        parentState.setInitialState(newState);
                    }
                }

                Action action = createEntryAction(state);
                newState.addEntryAction(action);
                action = createExitAction(state);
                newState.addExitAction(action);
            }

            for (FsmState state : fsmStates) {
                for (FsmTransit transition : state.getTransitions().stream().filter(t -> t.isValid()).collect(Collectors.toCollection(ArrayList::new))) {
                    On on = builder.transition().from(transition.getFrom()).to(transition.getTo()).on(transition.getOn());
                    if (!Strings.isNullOrEmpty(transition.getWhenMvel())) {
                        on.whenMvel(transition.getWhenMvel());
                    }
                }
            }
            cache.put(k, builder);
        }
        return builder;
    }

    /**
     * 进入状态
     **/
    private Action createEntryAction(FsmState state) {
        return new AbstractAction(state) {
            @Override
            public void execute(FsmState from, FsmState to, FsmEvent event, Map context, FsmStateMachine stateMachine) {
                FsmStateInstance fsi = findFsmStateInstance(stateMachine.getFlowInstance(), this.getState(), false);
                if (fsi == null) {
                    fsi = new FsmStateInstance();
                    fsi.setFsmFlowInstance(stateMachine.getFlowInstance());
                    fsi.setState(this.getState());
                    fsi.setEvent(event);
                    FsmState lastState = stateMachine.getCurrentState();
                    if (lastState != null) {
                        FsmStateInstance parent = findFsmStateInstance(stateMachine.getFlowInstance(), lastState, true);
                        if (parent != null) {
                            fsi.setParent(parent);
                        }
                    }
                    Date expired = calcExpired(fsi.getState(), stateMachine.getContext());
                    if (expired != null) fsi.setExpired(expired);
                    String deptFile = fsi.getFsmFlowInstance().getFsmFlow().getBusinessDept();
                    if (StringUtils.hasText(deptFile)) {
                        Object o = stateMachine.getContext().get(deptFile);
                        if (o != null) {
                            fsi.setInitDeptId(String.valueOf(o));
                        }
                    }
                    fsi = fsmStateInstanceRepository.save(fsi);
                }

                if (this.getState().isFinish()) {
                    completeFlow(stateMachine);
                    completeState(stateMachine, this.getState());
                } else {
                    createTask(fsi, event, stateMachine.getContext());
                }
            }

            /**
             * 计算过期日期
             **/
            public Date calcExpired(FsmState state, Map context) {
                int duration = state.calcDuration();
                if (duration == 0) return null;
                String baseValue = state.getBaseValue();
                Date expired = (Date) context.getOrDefault(baseValue, new Date());
                if (state.getWorkSchedule() == null) {
                    expired = DateUtil.add(expired, Calendar.MINUTE, duration);
                } else {
                    expired = state.getWorkSchedule().calcExpired(expired, duration);
                }
                context.put("节点考核时间", expired);
                return expired;
            }

            /**创建任务**/
            private void createTask(FsmStateInstance fsi, FsmEvent event, Map context) {
                fsi.getState().getTasks().stream().filter(fsmTask -> fsmTask.isValid()).forEach(fsmTask -> {
                    context.put("当前时间", new Date());
                    TaskParam taskParam = FsmUtil.execute(fsmTask.getTimeExpr(), context);
                    List users = getUsers(fsmTask.getUserExpr(), context);
                    if (users == null || users.isEmpty()) return;
                    context.put("用户", users);
                    context.put("状态实例", fsi.getId());
                    FsmStateInstanceTask task = new FsmStateInstanceTask();
                    task.setTaskId(fsmTask.getId());
                    task.setState(fsi.getState());
                    task.setFsmStateInstance(fsi);
                    task.setFsmFlowInstance(fsi.getFsmFlowInstance());
                    task.setDelay(taskParam.getDelay());
                    task.setRepeat(taskParam.getRepeat());
                    task.setTimeUnit(taskParam.getTimeUnit());
                    task.setAlgorithm(taskParam.getAlgorithm());
                    task.setNextTime(taskParam.getNext());
                    task.setActionExpr(fsmTask.getActionExpr().replaceAll("\\.", "_"));
                    task.setParams(getParam(fsmTask.getActionExpr(), context));
                    taskRepository.save(task);
                });
            }

            private List getUsers(String express, Map context) {
                List users = new ArrayList();
                Object result = FsmUtil.execute(express, context);
                if (result instanceof Collection) {
                    users.addAll((Collection) result);
                } else {
                    users.add(result);
                }
                if (users.isEmpty()) {
                    //throw new RuntimeException("任务没有有效的执行人：" + express);
                }
                return (List) users.stream().distinct().collect(Collectors.toList());
            }

            private String getParam(String express, Map context) {
                String replacement = "#@";
                express = express.replaceAll("\\.", replacement);
                Map result = new HashMap();
                String[] names = FsmUtil.getOutVarNames(express);
                if (names != null) {
                    for (String k : names) {
                        if (k.contains(replacement)) {
                            k = k.replaceAll(replacement, "\\.");
                        }
                        Object v = null;
                        try {
                            v = CacheUtil.getEngine().execute(k, context);
                            if (v instanceof Date) {
                                v = DateUtil.format((Date) v, DateUtil.SHOW_DATETIME_FORMAT);
                            }
                        } catch (Exception e) {
                        }
                        result.put(k.replaceAll("\\.", "_"), v);
                    }
                }
                return JSON.toJSONString(result, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty);
            }
        };
    }

    private void completeFlow(FsmStateMachine stateMachine) {
        FsmFlowInstance flowInstance = stateMachine.getFlowInstance();
        flowInstance.setFinished(true);
        flowInstance = fsmFlowInstRepository.saveAndFlush(flowInstance);
        stateMachine.setFlowInstance(flowInstance);
    }

    /**
     * 离开状态
     **/
    private Action createExitAction(FsmState state) {
        return new AbstractAction(state) {
            @Override
            public void execute(FsmState from, FsmState to, FsmEvent event, Map context, FsmStateMachine stateMachine) {
                completeState(stateMachine, this.getState());
            }
        };
    }

    private void completeState(FsmStateMachine stateMachine, FsmState currentState) {
        FsmStateInstance fsi = findFsmStateInstance(stateMachine.getFlowInstance(), currentState, false);
        fsi.setFinished(true);
        String userId = getExecutor(stateMachine);
        if (!StringUtils.isEmpty(userId)) {
            fsi.setExecutor(userId);
        }
        fsmStateInstanceRepository.save(fsi);

        /**终止任务**/
        List<FsmStateInstanceTask> tasks = taskRepository.findByFsmStateInstanceAndFinishedIsFalse(fsi);
        tasks.stream().filter(task -> !task.isFinished()).forEach(task -> task.setFinished(true));
        taskRepository.save(tasks);
    }

    private String getExecutor(FsmStateMachine stateMachine) {
        Map context = stateMachine.getContext();
        String userId = null;
        if (context.containsKey("sessionId") && !StringUtils.isEmpty(context.get("sessionId"))) {
            String sessionId = (String) context.get("sessionId");
            ApesSession session = SpringManager.getBean(ApesSessionRepository.class).findOne(sessionId);
            if (session != null) {
                userId = session.getUserId();
            }
        }
        if (StringUtils.isEmpty(userId) && context.containsKey("updater") && !StringUtils.isEmpty(context.get("updater"))) {
            try {
                List<String> users = (List<String>) CacheUtil.getEngine().execute("按角色获取用户(updater)", context);
                if (users != null && users.size() > 0) {
                    userId = users.stream().filter(id -> StringUtils.hasText(id)).findFirst().orElse(null);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return userId;
    }

    private FsmStateInstance findFsmStateInstance(FsmFlowInstance fi, FsmState state, boolean finished) {
        return fsmStateInstanceRepository.findAll(
                JpaDsl.toCriteriaByEq("fsmFlowInstance", fi, "state", state, "finished", finished),
                new Sort(Sort.Direction.DESC, "id"))
                .stream().findAny().orElse(null);
    }

    @Data
    abstract class AbstractAction implements Action<FsmStateMachine, FsmState, FsmEvent, Map> {
        private FsmState state;

        public AbstractAction(FsmState state) {
            this.state = state;
        }

        @Override
        public String name() {
            return "exitAction:" + state.getFsmFlow().getName() + "：" + state.getName();
        }

        @Override
        public int weight() {
            return 0;
        }

        @Override
        public boolean isAsync() {
            return false;
        }

        @Override
        public long timeout() {
            return 0;
        }
    }

    /**
     * 清除状态机构造器
     **/
    private void removeStateMachineBuilder(String fsmFlowId) {
        String k = "fsm_builder_" + fsmFlowId;
        cache.remove(k);
    }

    /**
     * 创建状态机
     **/
    public FsmStateMachine createStateMachine(FsmFlowInstance fi) {
        StateMachineBuilder<FsmStateMachine, FsmState, FsmEvent, Map> builder = createStateMachineBuilder(fi.getFsmFlow());
        FsmStateMachine fsm = builder.newStateMachine((FsmState) JpaUtil.getImplementation(fi.getCurrentState()));
        StateMachineData.Reader smd = fi.deserialization();
        if (smd != null) {
            fsm.loadSavedData(smd);
        }
        return fsm;
    }

    /**
     * 创建状态机
     **/
    public FsmStateMachine createStateMachine(FsmFlow fsmFlow) {
        FsmState initialState = fsmFlow.findInitialState();
        if (initialState == null) return null;
        StateMachineBuilder<FsmStateMachine, FsmState, FsmEvent, Map> builder = createStateMachineBuilder(fsmFlow);
        return builder.newStateMachine(initialState);
    }
}
